#include <traceUtils/registerValue.idc>
class OutputUtils{
    OutputUtils(aslAddr, baseOutputFileName, outputFileName){
         this.ASL_addr = aslAddr;
         this.baseOutputFileName = baseOutputFileName;
         this.outputFileName = outputFileName;
         this.canDo = 1;

         if(this.getFileHandle() == 0){
            Warning("open file fail ！！！ \n");
            this.canDo = 0;
            return;
        }

        this.registerValue = RegisterValue();
        this.instructionsBCount = 0x1;
    }

    ~OutputUtils(){
        fclose(this.real_addr_fp);
        fclose(this.orig_addr_fp);
        fclose(this.orig_addr_step_param_fp);
        fclose(this.real_addr_step_param_fp);
        fclose(this.register_fp);
        fclose(this.bDetail_fp);
    }

    writeToFile(content){
        fprintf(this.real_addr_fp, content);
        fprintf(this.orig_addr_fp, content); 
    }

    getFileHandle(){
        Message("output real addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_real_addr.log");
        this.real_addr_fp = fopen(this.baseOutputFileName + this.outputFileName + "_real_addr.log", "w");
        if(this.real_addr_fp == 0){
            return 0;
        }

        Message("output orig addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_orig_addr.log");
        this.orig_addr_fp = fopen(this.baseOutputFileName + this.outputFileName + "_orig_addr.log", "w");
        if(this.orig_addr_fp == 0){
            return 0;
        }

        Message("output orig addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_orig_addr_step_param.log");
        this.orig_addr_step_param_fp = fopen(this.baseOutputFileName + this.outputFileName + "_orig_addr_step_param.log", "w");
        if(this.orig_addr_step_param_fp == 0){
            return 0;
        }

        Message("output orig addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_real_addr_step_param.log");
        this.real_addr_step_param_fp = fopen(this.baseOutputFileName + this.outputFileName + "_real_addr_step_param.log", "w");
        if(this.real_addr_step_param_fp == 0){
            return 0;
        }

        Message("output orig addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_register.log");
        this.register_fp = fopen(this.baseOutputFileName + this.outputFileName + "_register.log", "w");
        if(this.register_fp == 0){
            return 0;
        }

        Message("output orig addr file path: %s\n", this.baseOutputFileName + this.outputFileName + "_b_detail.log");
        this.bDetail_fp = fopen(this.baseOutputFileName + this.outputFileName + "_b_detail.log", "w");
        if(this.bDetail_fp == 0){
            return 0;
        }
        return 1;
    }

    stringComplementaryLength(str, len){
        auto lenTmp, differenceLen, spaceStr, i;
        lenTmp = strlen(str);
        if(lenTmp > len){
            return str;
        }

        differenceLen = len - lenTmp;
        spaceStr = "";
        for(i = 0; i < differenceLen; i++){
            spaceStr = spaceStr + " ";
        }
        return str + spaceStr;
    }

    outputByAddr(addr, instructionsCount){
        auto inst, disAsm, comment, addrSegmentName, logPrefix, origLogPrefix;
        inst = FindCode(PrevAddr(addr), SEARCH_DOWN | SEARCH_NEXT);
        disAsm = GetDisasm(inst);
        addrSegmentName = SegName(addr);
        addrSegmentName = this.stringComplementaryLength(addrSegmentName, 13);
        disAsm = this.stringComplementaryLength(disAsm, 50);

        // this.outputRegister(addr, instructionsCount);
        // this.outputBFunc(addr, instructionsCount, disAsm);

        logPrefix = form("[%08x] [%s] 0x%x: %s    ## ", instructionsCount, addrSegmentName, addr, disAsm);
        origLogPrefix = form("[%08x] [%s] 0x%x: %s    ## ", instructionsCount, addrSegmentName, addr - this.ASL_addr, disAsm);
        if(GetOperandValue(addr, 4) != 0xffffffffffffffff){
            this.outputAddParam(addr, 5, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 3) != 0xffffffffffffffff){
            this.outputAddParam(addr, 4, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 2) != 0xffffffffffffffff){
            this.outputAddParam(addr, 3, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 1) != 0xffffffffffffffff){
            this.outputAddParam(addr, 2, logPrefix, origLogPrefix);
            return 1;
        }

        if(GetOperandValue(addr, 0) != 0xffffffffffffffff){
            this.outputAddParam(addr, 1, logPrefix, origLogPrefix);
            return 1;
        }

        this.outputAddParam(addr, 0, logPrefix, origLogPrefix);
        return 1;
    }

    outputBFunc(addr, instructionsCount, disAsm){
        auto mnem = GetMnem(addr);
        if(!(strstr(mnem, "B") != -1 && mnem != "SUB" && mnem != "BKPT" && mnem != "BIC" && mnem != "QSUB" && mnem != "QDSUB" && mnem != "RSB" && mnem != "SBC")){
            return;
        }
        
        fprintf(this.bDetail_fp, "[%08x] - INSN: [%08x] - %s\n", this.instructionsBCount, instructionsCount, disAsm);
        auto i;
        for(i = 0; i < 32; i = i + 2){
            fprintf(this.bDetail_fp, "    X%d: %s, X%d: %s\n", i, this.registerValue.getValue(i, 80), i + 1, this.registerValue.getValue(i + 1, 80));
        }
        fprintf(this.bDetail_fp, "\n");
        this.instructionsBCount = this.instructionsBCount + 1;
    }

    outputRegister(addr, instructionsCount){
        fprintf(this.register_fp, "[%08x]:\n", instructionsCount);

        auto i;
        for(i = 0; i < 32; i = i + 2){
            fprintf(this.register_fp, "    X%d: %s, X%d: %s\n", i, this.registerValue.getValue(i, 80), i + 1, this.registerValue.getValue(i + 1, 80));
        }

        fprintf(this.register_fp, "\n");
    }

    outputAddParam(addr, count, logPrefix, origLogPrefix){
        Message("%s\n", logPrefix);
        fprintf(this.real_addr_fp, "%s\n", logPrefix);
        fprintf(this.orig_addr_fp, "%s\n", origLogPrefix);
        fprintf(this.orig_addr_step_param_fp, "%s\n", origLogPrefix);
        fprintf(this.real_addr_step_param_fp, "%s\n", logPrefix);
    }

    paramRealValue(addr, count){
        auto i, valueType, realValue, addrType;
        auto realResult = "";

        addrType = this.getAddrType(addr);
        this.registerValue.addrType = addrType;
        if(addrType == 1){
            realResult = form("X1=%s", this.registerValue.getValue("X1", 80));
            return realResult;
        }

        for(i = 0; i < count; i++){
            valueType = GetOpnd(addr, i);
            realValue = this.registerValue.getValue(valueType, 80);
            if(realResult == ""){
                realResult = form("%s=%s", valueType, realValue);
                continue;
            }

            realResult = form("%s, %s=%s", realResult, valueType, realValue);
        }
        return realResult;
    }

    paramValue(addr, count){
        auto i, valueType, realValue, addrType;
        auto result = "";
        for(i = 0; i < count; i++){
            valueType = GetOpnd(addr, i);
            if(result == ""){
                result = form("%s=0x%x", valueType, GetOperandValue(addr, i));
                continue;
            }

            result = form("%s, %s=0x%x", result, valueType, GetOperandValue(addr, i));
        }

        addrType = this.getAddrType(addr);
        this.registerValue.addrType = addrType;
        if(addrType == 1){
            result = form("%s, X1=%s", result, this.registerValue.getValue("X1", 80));
        }
        return result;
    }

    /*
    type value
    1         B BL    _objc_msgSend
    */
    getAddrType(addr){
        auto mnem, inst, disAsm;
        mnem = GetMnem(addr);
        if(mnem == "BL" || mnem == "B"){
            inst = FindCode(PrevAddr(addr), SEARCH_DOWN | SEARCH_NEXT);
            disAsm = GetDisasm(inst);
            if(strstr(disAsm, "_objc_msgSend") != -1){
                return 1;
            }
        }
        return 0;
    }
}